package com.farmer.redpacket.service;

import com.alibaba.fastjson.JSONObject;
import com.farmer.redpacket.controller.Websocket;
import com.farmer.redpacket.entity.RedPacket;
import com.farmer.redpacket.entity.RedPacketDetail;
import com.farmer.redpacket.entity.RedPacketDetailExample;
import com.farmer.redpacket.entity.RedPacketExample;
import com.farmer.redpacket.exceptions.ExceptionType;
import com.farmer.redpacket.exceptions.ServiceException;
import com.farmer.redpacket.inout.in.ReqOrderPayCallbackVO;
import com.farmer.redpacket.inout.in.ReqReceiveListVO;
import com.farmer.redpacket.inout.in.ReqReceiveRedPacketVO;
import com.farmer.redpacket.inout.in.ReqSendRedPacketsVO;
import com.farmer.redpacket.inout.out.ReceiveRecordVO;
import com.farmer.redpacket.inout.out.RespReceiveListVO;
import com.farmer.redpacket.mapper.RedPacketDetailMapper;
import com.farmer.redpacket.mapper.RedPacketMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import static com.farmer.redpacket.constants.TotalConstants.*;

/**
 * @author tech-farmer
 * @ClassName: RedPacketOrderService
 * @Description: (这里用一句话描述这个类的作用)
 * @date 2021/11/17 14:25
 */
@Service
@Slf4j
public class RedPacketOrderService {

    private final RedPacketMapper redPacketMapper;

    private final RedisTemplate<String, Object> redisTemplate;

    private final RedPacketDetailMapper detailMapper;

    public RedPacketOrderService(RedPacketMapper redPacketMapper, RedisTemplate<String, Object> redisTemplate, RedPacketDetailMapper detailMapper) {
        this.redPacketMapper = redPacketMapper;
        this.redisTemplate = redisTemplate;
        this.detailMapper = detailMapper;
    }


    public RedPacket generateRedPacket(ReqSendRedPacketsVO data,String userId) {
        final BigDecimal amount = data.getAmount();
        //红包数量
        final Integer num = data.getNum();

        //初始化订单
        final RedPacket redPacket = new RedPacket();
        redPacket.setPacketNo(UUID.randomUUID().toString().replace("-", ""));
        redPacket.setAmount(amount);
        redPacket.setNum(num);
        redPacket.setUserId(userId);
        Date now = new Date();
        redPacket.setCreateTime(now);
        redPacket.setUpdateTime(now);
        int i = redPacketMapper.insertSelective(redPacket);
        if (i != 1) {
            throw new ServiceException("生成红包出错", ExceptionType.SYS_ERR);
        }

        //模拟收银台下单
        String paySeq = UUID.randomUUID().toString().replace("-", "");

        //拿到收银台下单结果，更新订单为待支付状态
        redPacket.setOrderStatus(1);//待支付
        redPacket.setPaySeq(paySeq);
        i = redPacketMapper.updateByPrimaryKeySelective(redPacket);
        if (i != 1) {
            throw new ServiceException("生成红包出错", ExceptionType.SYS_ERR);
        }
        return redPacket;
    }

    @Transactional(rollbackFor = Exception.class)
    public void dealAfterOrderPayCallback(String userId,ReqOrderPayCallbackVO data) {
        RedPacketExample example = new RedPacketExample();
        final String packetNo = data.getPacketNo();
        final String paySeq = data.getPaySeq();
        final Integer payStatus = data.getPayStatus();
        example.createCriteria().andPacketNoEqualTo(packetNo)
                .andPaySeqEqualTo(paySeq)
                .andOrderStatusEqualTo(1);//待支付状态
        //更新订单支付状态
        Date now = new Date();
        RedPacket updateRedPacket = new RedPacket();
        updateRedPacket.setOrderStatus(payStatus);
        updateRedPacket.setUpdateTime(now);
        updateRedPacket.setPayTime(now);
        int i = redPacketMapper.updateByExampleSelective(updateRedPacket, example);
        if (i != 1) {
            throw new ServiceException("订单状态更新失败", ExceptionType.SYS_ERR);
        }

        if (payStatus == 2) {
            RedPacketExample query = new RedPacketExample();
            query.createCriteria().andPacketNoEqualTo(packetNo)
                    .andPaySeqEqualTo(paySeq)
                    .andOrderStatusEqualTo(2);
            final RedPacket redPacket = redPacketMapper.selectByExample(query).get(0);
            final List<BigDecimal> detailList = getRedPacketDetail(redPacket.getAmount(), redPacket.getNum());
            final int size = detailList.size();
            if (size <= 100) {
                i = detailMapper.batchInsert(detailList, redPacket);
                if (size != i) {
                    throw new ServiceException("生成红包失败", ExceptionType.SYS_ERR);
                }
            } else {
                int times = size % 100 == 0 ? size / 100 : (size / 100 + 1);
                for (int j = 0; j < times; j++) {
                    int fromIndex = 100 * j;
                    int toIndex = 100 * (j + 1) - 1;
                    if (toIndex > size - 1) {
                        toIndex = size - 1;
                    }
                    final List<BigDecimal> subList = detailList.subList(fromIndex, toIndex);
                    i = detailMapper.batchInsert(subList, redPacket);
                    if (subList.size() != i) {
                        throw new ServiceException("生成红包失败", ExceptionType.SYS_ERR);
                    }
                }
            }

            final String redisKey = REDPACKET_NUM_PREFIX + redPacket.getPacketNo();

            String lua = "local i = redis.call('setnx',KEYS[1],ARGV[1])\r\n" +
                    "if i == 1 then \r\n" +
                    "   local j = redis.call('expire',KEYS[1],ARGV[2])\r\n" +
                    "end \r\n" +
                    "return i";
            //优化成lua脚本
            final Long execute = redisTemplate.execute(new DefaultRedisScript<>(lua, Long.class), Arrays.asList(redisKey), size, 3600 * 24);
            if (execute != 1L) {
                throw new ServiceException("生成红包失败", ExceptionType.SYS_ERR);
            }

            Websocket.sendMessageToUser(userId, JSONObject.toJSONString(redPacket));
        }
    }


    /**
     * 红包水机算法
     *
     * @param amount 红包金额
     * @param num    红包数量
     * @return 随机红包集合
     */
    private List<BigDecimal> getRedPacketDetail(BigDecimal amount, Integer num) {
        List<BigDecimal> redPacketsList = new ArrayList<>(num);
        //最小红包金额
        final BigDecimal min = new BigDecimal("0.01");
        //最少需要红包金额
        final BigDecimal bigNum = new BigDecimal(num);
        final BigDecimal atLastAmount = min.multiply(bigNum);
        //出去最少红包金额后剩余金额
        BigDecimal remain = amount.subtract(atLastAmount);
        if (remain.compareTo(BigDecimal.ZERO) == 0) {
            for (int i = 0; i < num; i++) {
                redPacketsList.add(min);
            }
            return redPacketsList;
        }

        final Random random = new Random();
        final BigDecimal hundred = new BigDecimal("100");
        final BigDecimal two = new BigDecimal("2");
        BigDecimal redPacket;
        for (int i = 0; i < num; i++) {
            if (i == num - 1) {
                redPacket = remain;
            } else {
                //100内随机获得的整数
                final int rand = random.nextInt(100);
                redPacket = new BigDecimal(rand).multiply(remain.multiply(two).divide(bigNum.subtract(new BigDecimal(i)), 2, RoundingMode.CEILING)).divide(hundred, 2, RoundingMode.FLOOR);
            }
            if (remain.compareTo(redPacket) > 0) {
                remain = remain.subtract(redPacket);
            } else {
                remain = BigDecimal.ZERO;
            }
            redPacketsList.add(min.add(redPacket));
        }

        return redPacketsList;
    }

    public String receiveOne(ReqReceiveRedPacketVO data) {
        final Long redPacketId = data.getPacketId();
        final String redPacketNo = data.getPacketNo();
        final String redisKey = REDPACKET_NUM_PREFIX + redPacketNo;
        if (!redisTemplate.hasKey(redisKey)) {
            return "红包已经过期";
        }
        final Integer num = (Integer) redisTemplate.opsForValue().get(redisKey);
        if (num <= 0) {
            return "红包已抢完";
        }
        RedPacketDetailExample example = new RedPacketDetailExample();
        example.createCriteria().andPacketIdEqualTo(redPacketId)
                .andReceivedEqualTo(1)
                .andUserIdEqualTo(data.getUserId());
        final List<RedPacketDetail> details = detailMapper.selectByExample(example);
        if (!details.isEmpty()) {
            return "该红包已经领取过了";
        }
        final String receiveKey = REDPACKET_RECEIVE_PREFIX + redPacketNo + ":" + data.getUserId();

        //优化成lua脚本
        String lua = "local i = redis.call('setnx',KEYS[1],ARGV[1])\r\n" +
                "if i == 1 then \r\n" +
                "   local j = redis.call('expire',KEYS[1],ARGV[2])\r\n" +
                "end \r\n" +
                "return i";
        //优化成lua脚本
        final Long execute = redisTemplate.execute(new DefaultRedisScript<>(lua, Long.class), Arrays.asList(receiveKey), 1, 5);
        if (execute != 1L) {
            return "太快了";
        }
        return "";
    }

    @Transactional(rollbackFor = Exception.class)
    public String openRedPacket(ReqReceiveRedPacketVO data) {
        final Long packetId = data.getPacketId();
        final String packetNo = data.getPacketNo();
        final String userId = data.getUserId();
        final String redisKey = REDPACKET_NUM_PREFIX + packetNo;
        Long num = null;
        try {
            final String receiveKey = REDPACKET_RECEIVE_PREFIX + packetNo + ":" + userId;
            if (!redisTemplate.hasKey(receiveKey)) {
                log.info("未获取到红包资格，packet:{},user:{}", packetNo, userId);
                throw new ServiceException("红包飞走了", ExceptionType.SYS_ERR);
            }
            redisTemplate.delete(receiveKey);
            if (!redisTemplate.hasKey(redisKey)) {
                log.info("红包过期了，packet:{}", packetNo);
                throw new ServiceException("红包飞走了", ExceptionType.SYS_ERR);
            }
            num = redisTemplate.opsForValue().increment(redisKey, -1);
            if (num < 0L) {
                log.info("红包领完了，packet:{}", packetNo);
                throw new ServiceException("红包飞走了", ExceptionType.SYS_ERR);
            }
            final int i = detailMapper.receiveOne(packetId, packetNo, userId);
            if (i != 1) {
                log.info("红包真的领完了，packet:{}", packetNo);
                throw new ServiceException("红包飞走了", ExceptionType.SYS_ERR);
            }
            RedPacketDetailExample example = new RedPacketDetailExample();
            example.createCriteria().andPacketIdEqualTo(packetId)
                    .andReceivedEqualTo(1)
                    .andUserIdEqualTo(userId);
            final List<RedPacketDetail> details = detailMapper.selectByExample(example);
            if (details.size() != 1) {
                log.info("已经领取过了，packet:{}，user:{}", packetNo, userId);
                throw new ServiceException("红包飞走了", ExceptionType.SYS_ERR);
            }
            //处理加款
            log.info("抢到红包金额{}，packet:{}，user:{}", details.get(0).getAmount(), packetNo, userId);
            final String listKey = REDPACKET_LIST_PREFIX + packetNo;
            redisTemplate.opsForList().leftPush(listKey,details.get(0));
            redisTemplate.expire(redisKey, 24, TimeUnit.HOURS);
            return "" + details.get(0).getAmount();
        } catch (Exception e) {
            if (num != null) {
                redisTemplate.opsForValue().increment(redisKey, 1L);
            }
            log.warn("打开红包异常", e);
            throw new ServiceException("红包飞走了", ExceptionType.SYS_ERR);
        }
    }

    public RespReceiveListVO receiveList(ReqReceiveListVO data) {
        //红包记录redisKey
        final String packetNo = data.getPacketNo();
        final String redisKey = REDPACKET_LIST_PREFIX + packetNo;
        if (!redisTemplate.hasKey(redisKey)) {
            RedPacketDetailExample example = new RedPacketDetailExample();
            example.createCriteria().andPacketNoEqualTo(packetNo)
                    .andReceivedEqualTo(1);
            final List<RedPacketDetail> list = detailMapper.selectByExample(example);
            redisTemplate.opsForList().leftPushAll(redisKey, list);
            redisTemplate.expire(redisKey, 24, TimeUnit.HOURS);
        }
        List retList = redisTemplate.opsForList().range(redisKey, 0, -1);
        final Object collect = retList.stream().map(item -> {
            final JSONObject packetDetail = (JSONObject) item;
            return ReceiveRecordVO.builder()
                    .amount(packetDetail.getBigDecimal("amount"))
                    .receiveTime(packetDetail.getDate("updateTime"))
                    .userId(packetDetail.getString("userId"))
                    .packetId(packetDetail.getLong("redpacketId"))
                    .packetNo(packetDetail.getString("redpacketNo"))
                    .build();
        }).collect(Collectors.toList());
        return RespReceiveListVO.builder().list((List) collect).build();
    }
}
